#ifndef __UART_H
#define __UART_H

#include "./dma.h"
#include <stddef.h>

namespace stm32f1::uart {

#define UART_INSTANCE(BASE) reinterpret_cast<USART_TypeDef *>(BASE)

template <uint32_t BASE, typename PERIPH> struct Uart : public PERIPH {

    static constexpr uint32_t BASE_VAL = BASE;

    static constexpr uint32_t DR_ADDR_VAL = BASE + offsetof(USART_TypeDef, DR);

    template <typename CFG> static inline void set_cfg() {
        UART_INSTANCE(BASE_VAL)->CR1 = 0x00;
        UART_INSTANCE(BASE_VAL)->CR2 = CFG::CR2;
        UART_INSTANCE(BASE_VAL)->CR3 = CFG::CR3;
        UART_INSTANCE(BASE_VAL)->BRR = PERIPH::FREQ_VAL / CFG::BAUD_RATE;
        UART_INSTANCE(BASE_VAL)->CR1 = CFG::CR1;
    }

    static inline bool is_idle() {
        return (UART_INSTANCE(BASE_VAL)->SR & USART_SR_IDLE) != 0x00;
    }

    static inline void clear_idle_flag() {
        uint32_t f = UART_INSTANCE(BASE_VAL)->DR;
    }
};

struct DefCfg {

    static constexpr uint32_t CR1 = 0x201C;
    static constexpr uint32_t CR2 = 0x00;
    static constexpr uint32_t CR3 = USART_CR3_DMAT | USART_CR3_DMAR;
    static constexpr uint32_t BAUD_RATE = 115200;
};

template <typename UART>
inline void write_with_dma(const void *buf, uint16_t count) {
    using TX_CHAN = typename dma::DmaSel<UART::BASE_VAL>::TX_CHAN;
    uint32_t buf_addr = reinterpret_cast<uint32_t>(buf);
    TX_CHAN::template set_cfg<dma::UartWriteCfg, UART::DR_ADDR_VAL>(buf_addr,
                                                                    count);
}

template <typename UART> inline void recv_with_dma(void *buf, uint16_t count) {
    using RX_CHAN = typename dma::DmaSel<UART::BASE_VAL>::RX_CHAN;
    uint32_t buf_addr = reinterpret_cast<uint32_t>(buf);
    RX_CHAN::template set_cfg<dma::UartRecvCfg, UART::DR_ADDR_VAL>(buf_addr,
                                                                   count);
}

using Uart1 = Uart<USART1_BASE, rcc::APB2Bus<RCC_APB2ENR_USART1EN>>;
using Uart2 = Uart<USART2_BASE, rcc::APB1Bus<RCC_APB1ENR_USART2EN>>;
using Uart3 = Uart<USART3_BASE, rcc::APB1Bus<RCC_APB1ENR_USART3EN>>;
using Uart4 = Uart<UART4_BASE, rcc::APB1Bus<RCC_APB1ENR_UART4EN>>;

} // namespace stm32f1::uart

#endif
